/*
 * @(#)InstantMessage.java 0.0.1-SNAPSHOT
 *
 * Copyright Group B
 */

package grupb.upf.ms.impl;

import grupb.upf.Controller.Controller;
import grupb.upf.ms.SearchProfile;
import grupb.upf.ms.SearchResult;
import grupb.upf.ms.UserContent;
import java.util.List;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromContainsFilter;
import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smackx.muc.MultiUserChat;

/**
 * <p>This component implements an InstantMessage Interface. In this case,
 * it allows us to exchange information with Google Talk: login, logout,
 * send and receive Searches, send and receive Search Results, and send and
 * receive UserContent messages.</p>
 *
 * <p>This component has contains the listeners of the dif ferent kind
 * of messages. It is important to say that this listeners aren't
 * definetively and maybe they will be in separately classes. Furthermore,
 * here we simulate the search into the database but it isn't
 * implemented yet.</p>
 *
 * @version    0.0.1-SNAPSHOT
 * @author     Group B
 */
public final class InstantMessage implements grupb.upf.ms.InstantMessage {

    /**
     * The logger. A new logger <code>grupb.upf.ms.InstantMessage</code> is
     * created to add the logs of this InstantMessage.
     */
    private static final Log LOG = LogFactory.getLog(InstantMessage.class);
    /**
     * The serialVersionUID is created to be able to serialize this class,
     * it's necessary because it will be sent.
     */
    private static final long serialVersionUID = 1L;
    /**
     * The controller of the application.
     */
    private Controller controller;
    /**
     * The channel to listen to.
     */
    private MultiUserChat chat = null;
    /**
     * The port to connect to Google Talk.
     */
    private static final int GPORT = 5222;
    /**
     * The connection to Google Talk.
     */
    private XMPPConnection con = null;
    /**
     * The list of connected users to the channel.
     */
    private List < String > connectedUsers = new Vector();

    /**
     * This is the constructor of InstantMessage, it is not the final version.
     * We will study in next deliverables how to the GUI and see if it is
     * the better way to do this or not. It's a temporal solution.
     * @param guiController it's the GUI controller.
     */
    public InstantMessage(final Controller guiController) {
        this.controller = guiController;
    }
    /**
     * This method allows us to connect and identif icate to GoogleTalk.
     * @param aLogin is the userName of the Google Account (account@gmail.com)
     * @param aPassword is the password of the Google Account
     * @param resource is the resource, usually "Home"
     * @return boolean (true if it has logged and false if not)
     */
    @Override
    public boolean login(final String aLogin, final String aPassword,
            final String resource) {
        boolean flag = true;
        final ConnectionConfiguration config =
                new ConnectionConfiguration("talk.google.com",
                GPORT,
                "gmail.com");
        config.setSASLAuthenticationEnabled(true);
        config.setReconnectionAllowed(true);
        con = new XMPPConnection(config);
        try {
            con.connect();
            flag = true;
            controller.connectionState(flag);
        } catch (XMPPException ex) {
            if (LOG.isFatalEnabled()) {
                LOG.fatal("Error connecting through a new XMPPConnection", ex);
            }
            flag = false;
            controller.connectionState(flag);
        }
        if (flag) {
            try {
                con.login(aLogin, aPassword, resource);
                flag = true;
            } catch (XMPPException ex) {
                if (LOG.isFatalEnabled()) {
                    LOG.fatal("Error login", ex);
                }
                flag = false;
            }
        }
        if (flag) {
            // Create the presence object with default "P2Piki" availability
            final Presence presence = new Presence(Presence.Type.available);
            presence.setStatus("P2Piki connection");
            presence.setMode(Presence.Mode.available);
            // Send the presence packet through the connection
            con.sendPacket(presence);
            if (LOG.isInfoEnabled()) {
                final StringBuffer sb = new StringBuffer(con.getUser());
                sb.append(" logged in correctly");
                LOG.info(sb);
            }
            // Los Filtros
            final PacketFilter filterSearchProfile =
                    new AndFilter(new PacketTypeFilter(Message.class),
                    new PacketIDFilter("SearchProfile"),
                    new NotFilter(new FromContainsFilter(con.getUser())));
            final PacketFilter filterSearchResult =
                    new AndFilter(new PacketTypeFilter(Message.class),
                    new PacketIDFilter("SearchResult"),
                    new NotFilter(new FromContainsFilter(con.getUser())));
            final PacketFilter filterUserContent =
                    new AndFilter(new PacketTypeFilter(Message.class),
                    new PacketIDFilter("UserContent"),
                    new NotFilter(new FromContainsFilter(con.getUser())));
            final PacketFilter filterPresence =
                    new AndFilter(new PacketTypeFilter(Presence.class),
                    new NotFilter(new FromContainsFilter(con.getUser())));
            final PacketFilter filterContent =
                    new AndFilter(new PacketTypeFilter(Message.class),
                    new PacketIDFilter("Content"),
                    new NotFilter(new FromContainsFilter(con.getUser())));
            final PacketFilter filterInstantMessage =
                    new AndFilter(new PacketTypeFilter(Message.class),
                    new PacketIDFilter("InstantMessage"),
                    new NotFilter(new FromContainsFilter(con.getUser())));
            // Create the Packet Collectors
            con.createPacketCollector(filterSearchProfile);
            con.createPacketCollector(filterSearchResult);
            con.createPacketCollector(filterUserContent);
            con.createPacketCollector(filterPresence);
            con.createPacketCollector(filterContent);
            con.createPacketCollector(filterInstantMessage);
            // Starts the Multichat
            chat = new MultiUserChat(con, "P2Piki");
            //Creamos los listeners
            final PacketListener searchProfileListener =
                    new grupb.upf.ms.listeners.SearchProfileListener(
                    controller);
            final PacketListener searchResultListener =
                    new grupb.upf.ms.listeners.SearchResultListener(
                    controller);
            final PacketListener userContentListener =
                    new grupb.upf.ms.listeners.UserContentListener(chat);
            final PacketListener presenceListener =
                    new grupb.upf.ms.listeners.PresenceListener(this);
            final PacketListener contentListener =
                    new grupb.upf.ms.listeners.ContentListener(controller);
            final PacketListener imListener =
                    new grupb.upf.ms.listeners.MessageListener(controller);
            //Añadimos los listeners con sus respectivos filtros
            con.addPacketListener(searchProfileListener, filterSearchProfile);
            con.addPacketListener(searchResultListener, filterSearchResult);
            con.addPacketListener(userContentListener, filterUserContent);
            con.addPacketListener(presenceListener, filterPresence);
            con.addPacketListener(contentListener, filterContent);
            con.addPacketListener(imListener, filterInstantMessage);
        }
        return flag;
    }
    /**
     * This method allows us to logout from GoogleTalk.
     */
    @Override
    public void logout() {
        if (con != null) {
            // Create the presence message with the status unavailable
            final Presence presence = new Presence(Presence.Type.unavailable);
            presence.setStatus("P2Piki connection");
            presence.setMode(Presence.Mode.available);
            // Send the presence packet through the connection
            con.sendPacket(presence);
            con.disconnect();
            // We clear the list of connected users
            if (LOG.isInfoEnabled()) {
                LOG.info("Logout is done");
            }
            this.connectedUsers.clear();
            controller.removeFriends();
        }
    }
    /**
     * Method to send a message.
     * @param msg the message to be sent.
     */
    @Override
    public void sendMessage(final Message msg) {
        if (LOG.isInfoEnabled()) {
            final StringBuffer sb = new StringBuffer("Sending ")
                .append(msg.getPacketID()).append(" to ").append(msg.getTo());
            LOG.info(sb);
        }
        try {
            chat.sendMessage(msg);
        } catch (XMPPException ex) {
            final StringBuffer sb = new StringBuffer("Fatal error sending")
                .append(msg.getPacketID()).append(" to ").append(msg.getTo());
            if (LOG.isFatalEnabled()) {
                LOG.fatal(sb, ex);
            }
            controller.addError(sb.toString());
        }
    }
    /**
     * This method allows us to send a SearchProfile.
     * @param search is the object SearchProfile which we are looking for
     */
    @Override
    public void sendSearchProfile(final SearchProfile search) {
        if (connectedUsers != null) {
            for (int i = 0; i < connectedUsers.size(); i++) {
                sendSearchProfile(search, connectedUsers.get(i));
            }
        }
    }
    /**
     * This method allows us to send a SearchProfile to a concrete user.
     * @param search the object SearchProfile which we are looking for
     * @param to the receiver of the searchProfile message
     */
    @Override
    public void sendSearchProfile(final SearchProfile search,
            final String to) {
        final Message msg = new Message();
        StringBuffer sb =
                        new StringBuffer(to);
        msg.setTo(sb.toString());
        msg.setPacketID("SearchProfile");
        msg.setProperty("SearchProfile", (Object) search);
        sendMessage(msg);
    }
    /**
     * This method is in charge to answer the searchProfile messages received.
     * @param search the search to send
     * @param to the user who has to receive the message
     */
    @Override
    public void sendSearchResult(final SearchResult search, final String to) {
        final Message msg = new Message();
        msg.setTo(to);
        msg.setPacketID("SearchResult");
        msg.setProperty("SearchResult", search);
        sendMessage(msg);
    }
    /**
     * This method allows us to send UserContent message.
     * @param uc has the description of the Thumbnail received and the
     * necessary information to send the message
     * @param to the destinator of this message
     */
    @Override
    public void sendUserContent(final UserContent uc, final String to) {
        final Message msg = new Message();
        msg.setTo(to);
        msg.setPacketID("UserContent");
        msg.setProperty("UserContent", uc);
        sendMessage(msg);
    }
    /**
     * This method allows us to send instant messages.
     * @param message the message to send
     * @param to the destinator of our message
     */
    @Override
    public void sendInstantMessage(final String message, final String to) {
        final Message msg = new Message();
        msg.setTo(to);
        msg.setPacketID("InstantMessage");
        msg.setSubject("InstantMessage");
        msg.setBody(message);
        sendMessage(msg);
    }
    /**
     * This method allows us to send a message to all the connected friends.
     * @param message the message to send
     */
    @Override
    public void sendChatMessage(final String message) {
        if (connectedUsers != null) {
            for (int i = 0; i < connectedUsers.size(); i++) {
                final Message msg = new Message();
                msg.setTo(connectedUsers.get(i));
                msg.setPacketID("InstantMessage");
                msg.setSubject("Chat");
                msg.setBody(message);
                sendMessage(msg);
            }
        }
    }

    /**
     * This function return the list of connected users.
     * @return the list of connected users
     */
    @Override
    public List getConnectedUsers() {
        return this.connectedUsers;
    }
    /**
     * Method to add a user to the list of connected users.
     * @param user the user to add
     */
    @Override
    public void addConnectedUser(final String user) {
        this.connectedUsers.add(user);
        controller.addFriend(user);
    }
    /**
     * Method to remove a user from the list of connected users.
     * @param user the user to remove
     */
    @Override
    public void setDisconnectedUser(final String user) {
        this.connectedUsers.remove(user);
        controller.removeFriend(user);
    }
}
